/*
 * Copyright (C) 2015 Daniel Uppenkamp
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.maurersmarbles.states;

import com.google.common.base.Preconditions;
import com.googlecode.maurersmarbles.Card;
import com.googlecode.maurersmarbles.GameLogic;
import com.googlecode.maurersmarbles.GameState;
import com.googlecode.maurersmarbles.InputEvent;
import com.googlecode.maurersmarbles.Marble;
import com.googlecode.maurersmarbles.Move;
import com.googlecode.maurersmarbles.Play;
import com.googlecode.maurersmarbles.Rectangle;
import com.googlecode.maurersmarbles.Screen;
import com.googlecode.maurersmarbles.drawables.CardDrawable;
import com.googlecode.maurersmarbles.drawables.Drawable;
import com.googlecode.maurersmarbles.drawables.MarbleDrawable;
import java.util.Map;

/**
 *
 * @author Daniel Uppenkamp
 */
public class PlayerPlayState extends TableState {

    /**
     * The PlayerPlayState instance.
     */
    private static final PlayerPlayState INSTANCE = new PlayerPlayState();
    /**
     * The amount of time (in millis) to move a marble.
     */
    protected static final long ANIMATION_TIME_MOVE_MARBLE = 500L;
    /**
     * The amount of time (in millis) to play a card.
     */
    protected static final long ANIMATION_TIME_PLAY_CARD = 500L;
    /**
     * The token in the status messages that is replaced with the player number.
     */
    protected static final String PLAYER_NUMBER_TOKEN = "#";
    /**
     * The status to display when a player is playing.
     */
    protected static final String STATUS_PLAYER_PLAYING = "Player " + PLAYER_NUMBER_TOKEN + " is Playing";
    /**
     * The status to display when the user is playing.
     */
    protected static final String STATUS_USER_PLAYING = "Executing Your Play";
    /**
     * Whether or not a player is being animated.
     */
    protected volatile boolean animatingPlay = false;
    /**
     * The player number.
     */
    protected int playerNumber;
    /**
     * The play to execute.
     */
    protected Play play;

    /**
     * Creates a new PlayerPlayState.
     */
    private PlayerPlayState() {
        // do nothing
    }

    /**
     * Animates the given card being played.
     *
     * @param screen the screen
     * @param gameState the game state
     * @param card the card to animate being played
     * @param duration the duration (in millis) for the animation
     */
    protected void animateCardBeingPlayed(Screen screen, GameState gameState, Card card, long duration) {

        Preconditions.checkNotNull(screen, "Null screen");
        Preconditions.checkNotNull(card, "Null card");
        Preconditions.checkNotNull(boardDrawable, "Null board drawable");

        Long currentTime;
        Long startTime = currentTime = System.currentTimeMillis();

        while (currentTime < (startTime + duration)) {

            Map<Object, Drawable> drawables = getDrawables(screen, gameState);

            Rectangle discardRectangle = boardDrawable.getRectangleForDiscardCard();
            Rectangle playerCardRectangle = getRectangleForPlayerCard(screen, gameState, playerNumber, card);

            drawables.remove(card);

            double weight = (double) (currentTime - startTime) / (double) duration;

            Rectangle rectangle = Rectangle.interpolate(playerCardRectangle, discardRectangle, weight);

            drawables.put(card, new CardDrawable(card, rectangle));

            screen.draw(drawables.values());

            currentTime = System.currentTimeMillis();

        }

    }

    /**
     * Animates the given move.
     *
     * @param screen the screen
     * @param gameState the game state
     * @param move the move
     * @param duration the duration (in millis) of the animation
     */
    protected void animateMove(Screen screen, GameState gameState, Move move, long duration) {

        Preconditions.checkNotNull(screen, "Null screen");
        Preconditions.checkNotNull(gameState, "Null game state");
        Preconditions.checkNotNull(move, "Null move");
        Preconditions.checkNotNull(boardDrawable, "Null board drawable");

        Long currentTime;
        Long startTime = currentTime = System.currentTimeMillis();

        Marble marble = gameState.getPlayers().get(move.getPlayerNumber()).getMarble(move.getMarbleNumber());
        int rgb = GameLogic.getMarbleColorForPlayer(gameState, marble.getPlayerNumber());

        while (currentTime < (startTime + duration)) {

            Map<Object, Drawable> drawables = getDrawables(screen, gameState);

            Rectangle startRectangle = boardDrawable.getRectangleForMarbleSpace(marble.getBoardIndex());
            Rectangle endRectangle = boardDrawable.getRectangleForMarbleSpace(move.getNewMarbleBoardIndex());

            drawables.remove(marble);

            double weight = (double) (currentTime - startTime) / (double) duration;

            Rectangle rectangle = Rectangle.interpolate(startRectangle, endRectangle, weight);

            drawables.put(marble, new MarbleDrawable(rgb, rectangle));

            screen.draw(drawables.values());

            currentTime = System.currentTimeMillis();

        }

    }

    /**
     * Returns the PlayerPlayScreenState instance.
     *
     * @param playerNumber the player number
     * @param play the play
     * @return the PlayerPlayScreenState instance
     */
    public static PlayerPlayState getInstance(int playerNumber, Play play) {
        INSTANCE.playerNumber = playerNumber;
        INSTANCE.play = play;
        return INSTANCE;
    }

    @Override
    public State enter(Screen screen, GameState gameState) {

        animatingPlay = true;

        // check to see if there was not a valid play available
        if (play == null) {
            return PlayerTurnState.getInstance(GameLogic.getNextPlayerNumber(playerNumber));
        }

        // animate the card being played
        if (screen != null) {
            animateCardBeingPlayed(screen, gameState, play.getCard(), ANIMATION_TIME_PLAY_CARD);
        }

        // update the game state
        GameLogic.applyCardBeingPlayed(gameState, playerNumber, play.getCard());

        // update the discarded card to the card that was played
        discardedCard = play.getCard();

        // loop through the play and animate/apply each move
        for (Move move : play.getMoves()) {

            // animate this move
            if (screen != null) {
                animateMove(screen, gameState, move, ANIMATION_TIME_MOVE_MARBLE);
            }

            // update the game state
            GameLogic.applyMove(gameState, move);

        }

        // draw the screen after all of the animations are complete
        if (screen != null) {
            screen.draw(getDrawables(screen, gameState).values());
        }

        animatingPlay = false;

        return PlayerTurnState.getInstance(GameLogic.getNextPlayerNumber(playerNumber));

    }

    @Override
    protected Map<Object, Drawable> getDrawables(Screen screen, GameState gameState) {

        Preconditions.checkNotNull(gameState, "Null game state");

        if (playerNumber != gameState.getUserPlayerNumber()) {
            gameState.setStatus(STATUS_PLAYER_PLAYING.replaceFirst(PLAYER_NUMBER_TOKEN, String.valueOf(playerNumber)));
        }
        else {
            gameState.setStatus(STATUS_USER_PLAYING);
        }

        return super.getDrawables(screen, gameState);

    }

    @Override
    public State handleInputEvent(Screen screen, GameState gameState, InputEvent inputEvent) {
        // do nothing and return null to stay in this state
        return null;
    }

    @Override
    public void handleResizeEvent(Screen screen, GameState gameState, int width, int height) {
        /**
         * Do nothing. This state does all of the work in the enter function and
         * then moves to the next state. If a resize event happens, the screen
         * will be redrawn on the next animation.
         */
    }

}
